package top.hihuzi.collection.utils;


import top.hihuzi.collection.exception.CalculateException;

import java.math.BigDecimal;

/**
 * tips  通用工具计算
 *
 * @author: hihuzi 2019/4/23 19:23
 */
public class Calc {

    /**
     * tips 进行数值转换
     * 在不报错的情况下转换为数字类型否则报错处理 convertSafe
     *
     * @param <T> the type parameter
     * @param t   the t
     * @return the big decimal
     * @author hihuzi 2019/8/16 9:19
     */
    public static <T> BigDecimal convertCheck(T t) {

        if (null == t) return null;
        else if (t.getClass().isArray()) {
            return convert(((Object[]) t)[0]);
        }
        return null;
    }

    /**
     * 非安全型类型转换
     *
     * @param <T> the type parameter
     * @param t   the t
     * @return big decimal
     */
    public static <T> BigDecimal convert(T t) {

        if (t instanceof Number) return new BigDecimal(((Number) t).doubleValue());
        else if (t instanceof String) {
            char[] chars = ((String) t).toCharArray();
            /* 存在报错可能*/
            for (char isNum : chars) {
                if (Character.isDigit(isNum)) return new BigDecimal((String) t);
                else throw new CalculateException("The value is can not convert NUMBER!");
            }
            throw new CalculateException("The value is can not convert NUMBER!");
        } else throw new CalculateException("The value is can not convert NUMBER!");
    }

    /**
     * tips BigDecimal 非安全型 加法 相加
     * 支持返回null  和无法转换为数值类型 返回异常
     *
     * @param <T> the type parameter
     * @param t   the t
     * @return big decimal <p> 争取转换成数值类型
     * @author hihuzi 2019/4/26 10:10
     */
    public static <T> BigDecimal add(final T... t) {

        return addPrecision(2, t);
    }

    /**
     * Add decimalPlaces big decimal.
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the t
     * @return the big decimal
     */
    public static <T> BigDecimal addPrecision(final int decimalPlaces, final T... t) {


        BigDecimal b0 = convertCheck(t);
        if (null == b0) return null;
        for (int i = 1; i < t.length; i++) {
            b0 = b0.add(convert(t[i]));
        }
        return b0.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * tips BigDecimal 非安全型 减法
     *
     * @param <T> the type parameter
     * @param t   the t
     * @return big decimal <p> 必须保证第一个数是减数 只有为空null时才设置为0 其它情况直接报异常
     * @author hihuzi 2019/4/26 10:10
     */
    public static <T> BigDecimal minus(final T... t) {

        return minusPrecision(2, t);
    }

    /**
     * Minus decimalPlaces big decimal.
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the t
     * @return the big decimal
     */
    public static <T> BigDecimal minusPrecision(final int decimalPlaces, final T... t) {

        BigDecimal b0 = convertCheck(t);
        if (null == b0) return null;
        for (int i = 1; i < t.length; i++) {
            b0 = b0.subtract(convert(t[i]));
        }
        return b0.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * tips BigDecimal 非安全型 相乘法
     *
     * @param <T> the type parameter
     * @param t   the t
     * @return big decimal
     * @author hihuzi 2019/4/26 10:10
     */
    public static <T> BigDecimal multiply(final T... t) {

        return multiplyPrecision(2, t);
    }

    /**
     * Multiply decimalPlaces big decimal.
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the t
     * @return the big decimal
     */
    public static <T> BigDecimal multiplyPrecision(final int decimalPlaces, final T... t) {

        BigDecimal b0 = convertCheck(t);
        if (null == b0) return null;
        for (int i = 1; i < t.length; i++) {
            b0 = b0.multiply(convert(t[i]));
        }
        return b0.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }


    /**
     * tips BigDecimal 非安全型 除法
     *
     * @param <T>   the type parameter
     * @param value the value
     * @return big decimal <p> BigDecimal的除法运算封装 ，如果除数或者被除数为0，  直接报异常
     * @author hihuzi 2019/4/26 10:10
     */
    public static <T> BigDecimal divide(final T... value) {

        return dividePrecision(2, value);
    }

    /**
     * Divide decimalPlaces big decimal.
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the t
     * @return the big decimal
     */
    public static <T> BigDecimal dividePrecision(final int decimalPlaces, final T... t) {

        BigDecimal b0 = convertCheck(t);
        if (null == b0) return null;
        for (int i = 1; i < t.length; i++) {
            b0 = b0.divide(convert(t[i]), decimalPlaces, BigDecimal.ROUND_HALF_UP);
        }
        return b0.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * tips BigDecimal 安全型 加法
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the t
     * @return the big decimal <p> 为 " " or null时才设置为0 其它情况无法转换为number的也处理为0
     * @author hihuzi 2019/4/26 10:09
     */
    public static <T> BigDecimal addSafe(final int decimalPlaces, final T... t) {


        BigDecimal result = castSafeCheck(t, 0);
        if (null == result) return null;
        for (int i = 1; i < t.length; i++) {
            result = result.add(castSafe(t[i], 0));
        }
        return result.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * tips BigDecimal 安全型 减法
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the value
     * @return the big decimal <p> 必须保证第一个数是减数 只有为空null时才设置为0 其它情况直接报异常
     * @author hihuzi 2019/4/26 11:06
     */
    public static <T> BigDecimal minusSafe(final int decimalPlaces, final T... t) {

        BigDecimal result = castSafeCheck(t, 0);
        if (null == result) return null;
        for (int i = 1; i < t.length; i++) {
            result = result.subtract(castSafe(t[i], 0));
        }
        return result.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * tips BigDecimal 安全型 乘法
     * 如果计算中出现null 直接置换成0
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the t
     * @return the big decimal
     * @author hihuzi 2019/4/29 17:43
     */
    public static <T> BigDecimal multiplySafe(final int decimalPlaces, T... t) {

        BigDecimal result = castSafeCheck(t, 0);
        if (null == result) return null;
        for (int i = 1; i < t.length; i++) {
            result = result.multiply(castSafe(t[i], 1));
        }
        return result.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * tips BigDecimal 安全型 除法
     *
     * @param <T>           the type parameter
     * @param decimalPlaces the decimalPlaces
     * @param t             the value
     * @return the big decimal @ <p> BigDecimal的除法运算封装 ，如果除数为0，返回null 如果被除数是0或者其他会被忽略(特例所有的分母都是0 返回分母)
     * @author hihuzi 2019/4/26 11:06
     */
    public static <T> BigDecimal divideSafe(final int decimalPlaces, final T... t) {

        BigDecimal result = castSafeCheck(t, 0);
        if (null == result) return null;
        for (int i = 1; i < t.length; i++) {
            result = result.divide(castSafe(t[i], 1), decimalPlaces, BigDecimal.ROUND_HALF_UP);
        }
        return result.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * tips 进行数值转换 无法转换直接设置为BigDecimal.ZERO
     * type 标识计算风格 加法减法无法转换为数字类型设置为BigDecimal.ZERO或者 乘法除法设置为 new bigDecimal("1")
     * cash
     *
     * @param <T>             待传参数类型
     * @param t               待传参数
     * @param unexpectedValue 意外值
     * @return the big decimal
     * @author hihuzi 2019/8/16 9:19
     */
    public static <T> BigDecimal castSafe(T t, Integer unexpectedValue) {
        return castSafe(t, null, unexpectedValue);
    }

    /**
     * Cast safe big decimal.
     *
     * @param <T>             the type parameter
     * @param t               待传参数
     * @param decimalPlaces   保留小数位
     * @param unexpectedValue 意外值
     * @return the big decimal
     */
    public static <T> BigDecimal castSafe(T t, final Integer decimalPlaces, Integer unexpectedValue) {
        return castSafe(t, decimalPlaces, null, unexpectedValue);
    }

    /**
     * Cast safe big decimal.
     * 默认精度不传 四舍五入  小数位不传默认原始值小数位
     *
     * @param <T>             the type parameter
     * @param t               the t
     * @param decimalPlaces   the decimal places
     * @param carryStrategy   the carry strategy
     * @param unexpectedValue the unexpected value
     * @return the big decimal
     */
    public static <T> BigDecimal castSafe(T t, Integer decimalPlaces, Integer carryStrategy, Integer unexpectedValue) {
        if (null == carryStrategy)
            carryStrategy = BigDecimal.ROUND_HALF_UP;
        if (t != null) {
            if (t instanceof Number) {
                if (t.equals(Float.NaN) || t.equals(Double.NaN))
                    return unexpectedValue == null ? null : new BigDecimal(String.valueOf(unexpectedValue));
                BigDecimal bigDecimal = new BigDecimal(String.valueOf(t));
                return bigDecimal.setScale(null == decimalPlaces ? bigDecimal.scale() : decimalPlaces, carryStrategy);
            } else if (t instanceof String) {
                if (((String) t).split("\\.").length > 2||((String) t).length()==0)
                    return unexpectedValue == null ? null : new BigDecimal(String.valueOf(unexpectedValue));
                String tem = ((String) t).replace(".", "");
                if (tem.startsWith("-"))
                    tem = tem.substring(1);
                char[] chars = tem.toCharArray();
                for (char isNum : chars)
                    if (!Character.isDigit(isNum))
                        return unexpectedValue == null ? null : new BigDecimal(String.valueOf(unexpectedValue));
                BigDecimal bigDecimal = new BigDecimal((String) t);
                return bigDecimal.setScale(null == decimalPlaces ? bigDecimal.scale() : decimalPlaces, carryStrategy);
            }
        }
        return unexpectedValue == null ? null : new BigDecimal(String.valueOf(unexpectedValue));
    }

    /**
     * tips 进行数值转换
     * 存在数字开头不是后面不是数据类型的无法处理 建议用安全类型 convertSafe
     * 可能存在报错
     *
     * @param <T>  the type parameter
     * @param t    the t
     * @param type the type
     * @return the big decimal
     * @author hihuzi 2019/8/16 9:19
     */
    public static <T> BigDecimal castSafeCheck(T t, int type) {

        if (null == t) return null;
        else if (t.getClass().isArray()) {
            return castSafe(((Object[]) t)[0], type);
        }
        return null;
    }

}
